﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Collections;

namespace FAST
{
    public class UpdateReconcilation
    {
        private const int FILE_IDENTICAL = 2;
        private const bool IS_FILE = true;
        private List<FilesHandler> conflictList;
        private MessageLog msgLog;

        //Constants declared for sync operation
        private const int DEFAULT = 0;
        private const int COPY_LEFT = 1;
        private const int COPY_RIGHT = 2;
        private const int DELETE = 3;

        //constant for conflict type
        private const int L_MOD_R_DEL = 0;
        private const int R_MOD_L_DEL = 1;
        private const int L_R_MOD = 2;
        private const int TIME_CON = 3;

        //to print timestamp for log.
        private DateTime currentTimeStamp;
        private const string DATE_PATT = @"M/d/yyyy hh:mm:ss.fff tt";

        //Method name : UpdateReconcilation() : N.A for constructors.
        //Parameters  : None.
        //Description :  An instance of UpdateReconcilation object.
        //Exceptions  :  None.
        public UpdateReconcilation()
        {
            this.conflictList = new List<FilesHandler>();
            this.msgLog = new MessageLog();

        }

        public UpdateReconcilation(List<FilesHandler> conflictList)
        {
            this.conflictList = conflictList;
            this.msgLog = new MessageLog();
        }


        //Method name : processStatus() : void
        //Parameters  : leftList(type List<FilesHandler>), rightList(type List<FilesHandler>), leftFoldPath(type string), rightFoldPath(type string)
        //Description :  Set file status of the left/right list object, version the FileHandler that is going to overwritten, handle any conflicting object and reconcile left/right list object. 
        //Exceptions  :  IndexOutOfRangeException: Accessing  or updating of Left/ Right list may incur this exception.
        public void processStatus(List<FilesHandler> leftList, List<FilesHandler> rightList, string leftFoldPath, string rightFoldPath)
        {
            List<int[]> leftConList = new List<int[]>();
            List<int[]> rightConList = new List<int[]>();
            List<string> conflictsDoNth = new List<string>();

            setStatus(leftList, rightList, leftConList, rightConList, rightFoldPath, true);
            setStatus(rightList, leftList, leftConList, rightConList, leftFoldPath, false);

            if (leftConList.Count != 0 && rightConList.Count != 0)
            {
                resolveConflict(leftList, rightList, leftConList, rightConList);
                updateConflictLog(leftList, rightList, leftConList, rightConList, leftFoldPath, rightFoldPath);

                //return conflict filepath for do nth, version file that are being replaced.
                versionConflicts(leftList, rightList, leftConList, rightConList);
            }

            reconcileFile(leftList, rightList, leftFoldPath, rightFoldPath);

        }


        //Method name : setStatus() : void
        //Parameters  : list1(type List<FilesHandler>), list2(type List<FilesHandler>), leftConList(type List<int[]>), rightConList(type List<int[]>), desFoldPath(type string), isLeftList(type bool)
        //Description : Compare left/right list object dirty bit and set their file status accordingly.
        //Exceptions  : IndexOutOfRangeException: Accessing  or updating of Left/ Right list may incur this exception.
        public void setStatus(List<FilesHandler> list1, List<FilesHandler> list2, List<int[]> leftConList, List<int[]> rightConList, string desFoldPath, bool isLeftList)
        {
            FileVersioning fileVer = new FileVersioning();

            int count = 0;
            while (count < list1.Count)
            {
                if (list1[count].getFileType() == false)
                {
                    FolderHandler foldHandler = (FolderHandler)list1[count];

                    string foldPathToCompare = desFoldPath + foldHandler.getSubFolderPath();
                    FolderHandler newFolder = new FolderHandler(foldPathToCompare);

                    if (foldHandler.getDirtyBit() == true)
                    {
                        //Set status for folder addition.
                        if (!list2.Contains((FolderHandler)newFolder))
                        {
                            if (isLeftList == true)
                            {
                                foldHandler.setFolderStatus(COPY_RIGHT);
                            }
                            else
                            {
                                foldHandler.setFolderStatus(COPY_LEFT);
                            }
                        }
                        //else modified op for folder => do nothing 
                    }
                    else
                    {
                        //Set status for folder deletion.
                        if (!list2.Contains(newFolder)) 
                        {
                            foldHandler.setFolderStatus(DELETE);
                        }
                        //else do nothing - folders are not modified.
                    }
                }
                else if (list1[count].getFileType() == true)
                {
                    FileHandler currentFile = (FileHandler)list1[count];
                    FileInfo fi = currentFile.getFi();
                    string filePathToCompare = "";

                    filePathToCompare = currentFile.getSubFilePath();

                    FileHandler newFile = new FileHandler(filePathToCompare);
                    int list2Index = list2.IndexOf(newFile);

                    if (currentFile.getDirtyBit() == true)
                    {
                        if (list2Index == -1) //this implies file does not exits in another list.
                        {
                            if (currentFile.isTimeConflictCheck())
                            {
                                //Set status for time conflict, where one file is modified.
                                if (isLeftList == true)
                                {
                                    int[] conLObj = { count, TIME_CON };
                                    leftConList.Add(conLObj);
                                    int[] conRObj = { list2Index, DEFAULT };

                                    rightConList.Add(conRObj);
                                }
                                else
                                {
                                    int[] conRObj = { count, TIME_CON };
                                    rightConList.Add(conRObj);
                                    int[] conLObj = { list2Index, DEFAULT };

                                    leftConList.Add(conLObj);
                                }
                            }
                            else if (currentFile.isInOSCheck())
                            {
                                //Set status for conflict type 1 where one files is modified and one file is deleted. 
                                if (isLeftList == true)
                                {
                                    int[] conLObj = { count, L_MOD_R_DEL };
                                    leftConList.Add(conLObj);
                                    int[] conRObj = { -1, L_MOD_R_DEL };

                                    rightConList.Add(conRObj);
                                }
                                else
                                {
                                    int[] conRObj = { count, R_MOD_L_DEL };

                                    rightConList.Add(conRObj);
                                    int[] conLObj = { -1, R_MOD_L_DEL };

                                    leftConList.Add(conLObj);
                                }

                            }
                            else
                            {
                                //Set status for file addition.
                                if (isLeftList == true)
                                {
                                    currentFile.setFileStatus(COPY_RIGHT);
                                }
                                else
                                {
                                    currentFile.setFileStatus(COPY_LEFT);
                                }

                                //set version bit for the file to be replaced.
                                if (list2Index != -1)
                                {
                                    fileVer.createVersion((FileHandler)list2[list2Index]);
                                }

                            }
                        }
                        else
                        {
                            // Set status for Conflict type 2 where both files are modified. 
                            // Set status for Conflict type 3 time tampering
                            if (((FileHandler)list2[list2.IndexOf(newFile)]).getDirtyBit() == true || currentFile.isTimeConflictCheck())
                            {
                                if (isLeftList == true)
                                {
                                    int[] conLObj = { count, L_R_MOD };
                                    int[] conRObj = { list2.IndexOf(newFile), L_R_MOD };
                                    if (currentFile.isTimeConflictCheck())
                                    {
                                        conLObj[1] = TIME_CON;
                                        conRObj[1] = TIME_CON;
                                    }

                                    leftConList.Add(conLObj);
                                    rightConList.Add(conRObj);

                                    currentFile.setFileStatus(DEFAULT);
                                }
                                else
                                {
                                    /* Might be redundant
                                     * if (rightConList.IndexOf(count) == -1)
                                     {
                                         rightConList.Add(count);
                                         leftConList.Add(list2.IndexOf(newFile));
                                     }*/
                                    currentFile.setFileStatus(DEFAULT);
                                }
                            }
                            else
                            {
                                //Set status when one of the file is modified only.
                                if (isLeftList == true)
                                {
                                    currentFile.setFileStatus(COPY_RIGHT);

                                }
                                else
                                {
                                    currentFile.setFileStatus(COPY_LEFT);

                                }

                                //preparing to version the corresponding file being replaced.
                                if (list2Index != -1)
                                {
                                    fileVer.createVersion((FileHandler)list2[list2Index]);
                                }
                            }
                        }
                    }
                    else
                    {
                        //Sets status for file deletion.
                        if (list2Index == -1)
                        {
                            currentFile.setFileStatus(DELETE);
                            //preparing to version itself.
                            fileVer.createVersion((FileHandler)list1[count]);
                        }
                        else
                        {
                            //Both file not modified, status set to default
                            currentFile.setFileStatus(DEFAULT);
                        }
                    }
                }
                count++;
            }
        }

        public void resolveConflict(List<FilesHandler> leftList, List<FilesHandler> rightList, List<int[]> left_con, List<int[]> right_con)
        {
            Conflicts con = new Conflicts(leftList, rightList, left_con, right_con /*, leftFoldPath, rightFoldPath*/);
            con.ShowDialog();

        }


        //Method name : versionConflicts() : void
        //Parameters  : leftList(type List<FilesHandler>), rightList(type List<FilesHandler>), leftConList(type List<int[]>), rightConList(type List<int[]>)
        //Description : Create versions of FileHandler after user decides an action.
        //Exceptions  : ArrayIndexOutOfBoundException: Reading elements that does not exist in the array, IndexOutOfRangeException: Accessing  or updating of Left/ Right list may incur this exception.
        public void versionConflicts(List<FilesHandler> leftList, List<FilesHandler> rightList, List<int[]> leftConList, List<int[]> rightConList)
        {
            int[] conLObj;
            int[] conRObj;
            List<string> conflictsDoNth = new List<string>();
            FileVersioning versioning = new FileVersioning();

            //here
            for (int i = 0; i < leftConList.Count; i++)
            {
                conLObj = leftConList[i];
                conRObj = rightConList[i];

                if (conLObj[0] != -1)
                {
                    if (((FileHandler)leftList[conLObj[0]]).getFileStatus() == COPY_RIGHT)
                    {
                        if (conRObj[0] != -1) //if right file does not exist, don't version!
                        {
                            versioning.createVersion((FileHandler)rightList[conRObj[0]]);
                        }
                    }
                    else if (((FileHandler)leftList[conLObj[0]]).getFileStatus() == DELETE)
                    {
                        versioning.createVersion((FileHandler)leftList[conLObj[0]]);
                    }
                }

                if (conRObj[0] != -1)
                {
                    if (((FileHandler)rightList[conRObj[0]]).getFileStatus() == COPY_LEFT)
                    {
                        if (conLObj[0] != -1)
                        {
                            versioning.createVersion((FileHandler)leftList[conLObj[0]]);
                        }
                    } 
                    else if (((FileHandler)rightList[conRObj[0]]).getFileStatus() == DELETE)
                    {
                        versioning.createVersion((FileHandler)rightList[conRObj[0]]);
                    }
                }
            }
        }

        public void reconcileFile(List<FilesHandler> leftList, List<FilesHandler> rightList, string leftFoldPath, string rightFoldPath)
        {

            //process from LeftList 
            processLROperations(leftList, rightList, rightFoldPath);

            //process from RightList 
            processLROperations(rightList, leftList, leftFoldPath);

        }

        public void copyFile(string srcFilePath, string desFilePath)
        {
            try
            {
                File.Copy(srcFilePath, desFilePath, true);

                currentTimeStamp = DateTime.Now;
                msgLog.appendAllMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] File Copied from \"" + srcFilePath + "\" to \"" + desFilePath + "\" successfully.\n");
                msgLog.appendChangesMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] File Copied from \"" + srcFilePath + "\" to \"" + desFilePath + "\" successfully.\n");

            }
            catch (Exception)
            {
                currentTimeStamp = DateTime.Now;
                msgLog.appendAllMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] Exception: File failed to copy from \"" + srcFilePath + "\" to \"" + desFilePath + "\". \n");
                msgLog.appendErrorMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] Exception: File failed to copy from \"" + srcFilePath + "\" to \"" + desFilePath + "\". \n");
            }
        }

        public void createNewDirectory(string fullFoldPath)
        {
            try
            {
                Directory.CreateDirectory(fullFoldPath);

                currentTimeStamp = DateTime.Now;
                msgLog.appendAllMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] New Folder created at \"" + fullFoldPath + "\" successfully.\n");
                msgLog.appendChangesMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] New Folder created at \"" + fullFoldPath + "\" successfully.\n");
            }
            catch (Exception)
            {
                currentTimeStamp = DateTime.Now;
                msgLog.appendAllMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] Exception: New Folder failed to create at \"" + fullFoldPath + "\". \n");
                msgLog.appendErrorMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] New Folder failed to create at \"" + fullFoldPath + "\". \n");
            }

        }

        public void deleteDirectory(string fullFoldPath)
        {
            try
            {
                if (Directory.Exists(fullFoldPath))
                {
                    Directory.Delete(fullFoldPath, true);
                }

                //not place inside the if so as to log the cascate delete - the list is not modified. => will attempt to delete deleted files.
                currentTimeStamp = DateTime.Now;
                msgLog.appendAllMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] Folder at \"" + fullFoldPath + "\" is deleted successfully.\n");
                msgLog.appendChangesMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] Folder at \"" + fullFoldPath + "\" is deleted successfully.\n");
            }
            catch (Exception)
            {
                currentTimeStamp = DateTime.Now;
                msgLog.appendAllMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] Exception: Folder failed to delete at \"" + fullFoldPath + "\".\n");
                msgLog.appendErrorMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] Folder failed to delete at \"" + fullFoldPath + "\".\n");
            }
        }

        public void deleteFile(string fullFilePath)
        {
            try
            {
                if (File.Exists(fullFilePath))
                {
                    File.Delete(fullFilePath);
                }

                currentTimeStamp = DateTime.Now;
                msgLog.appendAllMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] File from \"" + fullFilePath + "\" is deleted successfully.\n");
                msgLog.appendChangesMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] File from \"" + fullFilePath + "\" is deleted successfully.\n");
            }
            catch (Exception)
            {
                currentTimeStamp = DateTime.Now;
                msgLog.appendAllMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] Exception: File failed to delete at \"" + fullFilePath + "\".\n");
                msgLog.appendErrorMsg("[" + currentTimeStamp.ToString(DATE_PATT) + "] File failed to delete at \"" + fullFilePath + "\".\n");
            }
        }

        //List update for folder creation.
        public void updateListFoldChanges(string tempFullPath, FolderHandler currentFold, List<FilesHandler> list2)
        {
            DirectoryInfo newDi = new DirectoryInfo(tempFullPath);
            FolderHandler newFolder = new FolderHandler(tempFullPath, currentFold.getSubFolderPath(), newDi, false, 0);

            FolderHandler parent = new FolderHandler(newDi.Parent.FullName);
            int index = list2.IndexOf(parent);

            int noOfFiles = 0;
            noOfFiles = totalNoOfFilesInFold(newDi.Parent.FullName, tempFullPath);

            if (index != -1)
            {
                //exclude the folder itself
                list2.Insert(index + noOfFiles + 1, newFolder);
            }
            else
            {
                if (index + noOfFiles == -1)
                {
                    list2.Insert(0, newFolder);
                }
                else
                {
                    list2.Insert(noOfFiles, newFolder);
                }
            }

        }

        //overload for folder delete update.
        public void updateListFoldChanges(FolderHandler currentDelFold, List<FilesHandler> list1, int currentIndex)
        {
            //Update list
            DirectoryInfo foldHandlerDi = new DirectoryInfo(currentDelFold.getFullFolderPath());

            int noOfFiles = 0;
            noOfFiles = totalNoOfFilesInFold(currentDelFold.getFullFolderPath(), currentDelFold.getFullFolderPath()/*, noOfFiles*/);
            noOfFiles++;//include itself => + 1

            list1.RemoveRange(currentIndex, noOfFiles);
        }

        public void updateListFileChanges(string tempFullPath, string tempFilePath, FileHandler currentFile, List<FilesHandler> list2, bool isAddOp)
        {
            //Update list1 - revert bit.
            currentFile.setDirtyBit(false);
            //Update list2 - add op
            if (isAddOp)
            {
                FileHandler newFile = new FileHandler(tempFullPath, currentFile.getSubFilePath(), new FileInfo(tempFullPath), false, 0);
                newFile.setMd5HashCode(currentFile.getMd5HashCode());

                //rearrange the list elements according to their parent directory.
                string parentFoldPath = tempFullPath.Substring(0, tempFullPath.LastIndexOf('\\'));
                FolderHandler parent = new FolderHandler(parentFoldPath);
                int index = list2.IndexOf(parent);


                list2.Insert(index + 1, newFile);
            }
            else
            {
                //Update on Copy L to R or R to L.
                //Update des file's properties. => handling conflicts issues.
                //Where there exist a file in the list 2.
                FilesHandler compareFile = new FileHandler(tempFilePath);
                int desIndex = list2.IndexOf(compareFile);

                FileHandler newUpdatedFile = new FileHandler(tempFullPath, currentFile.getSubFilePath(), new FileInfo(tempFullPath), false, 0);
                newUpdatedFile.setMd5HashCode(currentFile.getMd5HashCode());
                newUpdatedFile.setVersioningIdx(((FileHandler)list2[desIndex]).getVersioningIdx());
                list2[desIndex] = newUpdatedFile;
                
            }
        }


        //Method name : processLROperations() : void
        //Parameters  : list1(type List<FilesHandler>), list2(type List<FilesHandler>), desFoldPath(type string)
        //Description : Check file or folder status and perform file operations to the physical files and folders location. Left/right list will be updated according to the final state of both Left/right location.
        //Exceptions  : IndexOutOfRangeException: Shifting position of list may incur this exception, FileNotFoundException: Invalid source or destination file path, DirectoryNotFoundException: Invalid source or destination directory path, UnauthorizedAccessException: Copy Read-only file to destination.
        public void processLROperations(List<FilesHandler> list1, List<FilesHandler> list2, string desFoldPath)
        {
            //New a version class
            FileVersioning versioning = new FileVersioning();

            //Processleft & right list => check each status for file and folder status(currently dun wan)
            int count = 0; //skip root folder
            while (count < list1.Count)
            {
                if (list1[count].getFileType() == false)
                {
                    //Handler Folders
                    FolderHandler currentFold = (FolderHandler)list1[count];

                    switch (currentFold.getFolderStatus())
                    {
                        case COPY_LEFT:
                        case COPY_RIGHT:
                            string tempFoldPath = desFoldPath + currentFold.getSubFolderPath();
                            createNewDirectory(tempFoldPath);
                            updateListFoldChanges(tempFoldPath, currentFold, list2);

                            break;

                        case DELETE:
                            /**Folder delete -Propagate delete
                             * On first entry, Folder will be physically deleted.
                             * But on virtual list(Left/right), will not be deleted yet.
                            **/
                            // folderDelete = true;
                            updateListFoldChanges(currentFold, list1, count);
                            deleteDirectory(currentFold.getFullFolderPath());
                            continue;
                        //break;
                    }
                }
                else
                {
                    //Handle Files.
                    FileHandler currentFile = (FileHandler)list1[count];
                    switch (currentFile.getFileStatus())
                    {
                        case COPY_LEFT:
                        case COPY_RIGHT:

                            //check if this operation is add new or modify.
                            bool isAddOp = false;
                            if (!File.Exists(desFoldPath + currentFile.getSubFilePath()))
                            {
                                isAddOp = true;
                            }

                            string tempFilePath = desFoldPath + currentFile.getSubFilePath();
                            copyFile(currentFile.getFullFilePath(), tempFilePath);
         

                            updateListFileChanges(tempFilePath, currentFile.getSubFilePath(), currentFile, list2, isAddOp);
                            break;

                        case DELETE:
                            deleteFile(currentFile.getFullFilePath());

                            //Update List1 - delete load
                            list1.RemoveAt(count);
                            continue;
                        //break;
                    }
                }
                count++;
            }
        }

        public void updateConflictLog(List<FilesHandler> leftList, List<FilesHandler> rightList, List<int[]> left_con, List<int[]> right_con, string lroot, string rRoot)
        {
            string tempMsg = "";
            int[] conLDetails = null;
            int[] conRDetails = null;
            for (int i = 0; i < left_con.Count; i++)
            {
                tempMsg = "";
                conLDetails = left_con[i];
                conRDetails = right_con[i];

                switch (conLDetails[1])
                {
                    case L_MOD_R_DEL:
                        tempMsg = "[Conflict Type 0] File is modified at \"" + ((FileHandler)leftList[conLDetails[0]]).getFullFilePath() + "\" and the corresponding file is deleted at right location.\n";
                        break;
                    case R_MOD_L_DEL:
                        tempMsg = "[Conflict Type 1] File is modified at \"" + ((FileHandler)rightList[conRDetails[0]]).getFullFilePath() + "\" and the corresponding file is deleted at the left location\n";
                        break;
                    case L_R_MOD:
                        tempMsg = "[Conflict Type 2] File is modified at both \"" + ((FileHandler)leftList[conLDetails[0]]).getFullFilePath() + "\" and \"" + ((FileHandler)rightList[conRDetails[0]]).getFullFilePath() + "\".\n";
                        break;
                    case TIME_CON:
                        tempMsg = "[Conflict Type 3] File's timestamp is modified at both \"" + ((FileHandler)leftList[conLDetails[0]]).getFullFilePath() + "\" and \"" + ((FileHandler)rightList[conRDetails[0]]).getFullFilePath() + "\".\n";
                        break;
                }

                int fileStat = -1;
                if (conLDetails[0] != -1)
                {
                    fileStat = ((FileHandler)leftList[conLDetails[0]]).getFileStatus();
                    if (conRDetails[0] == -1)
                    {
                        //L mod right delete. used back left index
                      
                    }else if (fileStat != ((FileHandler)rightList[conRDetails[0]]).getFileStatus() && ((FileHandler)rightList[conRDetails[0]]).getFileStatus() != DEFAULT)
                    {
                        fileStat = ((FileHandler)rightList[conRDetails[0]]).getFileStatus();
                    }
                }
                else
                {
                    fileStat = ((FileHandler)rightList[conRDetails[0]]).getFileStatus();
                }

                switch (fileStat)
                {
                    case COPY_LEFT:
                        tempMsg += "[Action Taken] You have chose to copy file from RIGHT location: \"" + ((FileHandler)rightList[conRDetails[0]]).getFullFilePath() + "\" and replaced the file in LEFT location: \"" + lroot + ((FileHandler)rightList[conRDetails[0]]).getSubFilePath() + "\"\n\n";
                        break;
                    case COPY_RIGHT:
                        tempMsg += "[Action Taken] You have chose to copy file from LEFT location: \"" + ((FileHandler)leftList[conLDetails[0]]).getFullFilePath() + "\" and replaced the file in RIGHT location: \"" + rRoot + ((FileHandler)leftList[conLDetails[0]]).getSubFilePath() + "\"\n\n";
                        break;
                    case DELETE:
                        tempMsg += "[Action Taken] You have chose to follow the delete operation of file in one of the two location.\n\n";
                        break;
                    case DEFAULT:
                        tempMsg += "[Action Taken] You have chose not to handle this conflict.\n\n";
                        break;
                }
                msgLog.appendConflictMsg(tempMsg);
            }
        }

        //Method name : generateMsgLog() : MessageLog
        //Parameters  : None.
        //Description : Return the MessageLog object created and populated during reconciliation of files and folder.
        //Exceptions  : None.
        public MessageLog generateMsgLog()
        {
            return this.msgLog;
        }

        //new method to recursively find the no of files inside the parent folder
        public int totalNoOfFilesInFold(string parentPath, string currentFold /*, int totalFiles*/)
        {

            int totalFiles = 0;

            string[] files = Directory.GetFiles(parentPath);

            totalFiles += files.Length;

            foreach (string folder in Directory.GetDirectories(parentPath))
            {
                if (!folder.Equals(currentFold))
                {
                    totalFiles++;
                    totalFiles += totalNoOfFilesInFold(folder, currentFold);
                }
            }
            return totalFiles;
        }
    }

}
